package com.example.bank.filter;

import cn.hutool.core.util.StrUtil;
import com.example.bank.exception.SystemException;
import com.example.bank.shiro.token.JWTToken;
import com.example.bank.util.JWTUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * @author ：Leeziqiang
 * @description：自定义的认证过滤器，用来拦截header中携带token的请求
 * @date ：2021/11/21 17:20
 */
@Component
public class JWTFilter extends BasicHttpAuthenticationFilter {

    private static final Logger logger = LoggerFactory.getLogger(JWTFilter.class);

    // 前置拦截处理
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        // servlet请求与响应的转换
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);

        // 跨域时首先会发送一个option请求，这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    // 后置拦截处理
    @Override
    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
        // 添加跨域支持
        this.fillCorsHeader(WebUtils.toHttp(request), WebUtils.toHttp(response));
    }

    // 过滤器拦截请求的入口方法，所有请求都会进入该方法，返回true则表示允许访问
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // 原来用来判断是否是请求登录请求，在本例中不会拦截登录请求，用来检测header中是否包含token字段
        if (this.isLoginRequest(request, response)) { // 源码中调用了isLoginAttempt()方法
            // 返回false后进入onAccessDenied()方法，返回错误信息
            return false;
        }
        boolean allowed = false;
        try {
            // 检测header里的JWT Token内容是否正确，尝试使用token进行登录
            allowed = this.executeLogin(request, response);
        } catch (IllegalStateException e) {
            // 未找到token
            e.printStackTrace();
            logger.info("未找到token");
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("token检验出错");
        }
        return allowed || super.isPermissive(mappedValue);
    }

    // 检测header中是否包含token字段
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        return ((HttpServletRequest) request).getHeader(JWTUtil.AUTH_HEADER) == null;
    }

    // 身份验证，检查JWT Token是否合法
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        // 从请求头里拿到token
        AuthenticationToken token = createToken(request, response);
        if (token == null || "".equals(token.getCredentials())) {
//            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.";
//            throw new IllegalStateException(msg);
            return false;
        }
        try {
            Subject subject = getSubject(request, response);
            // 让shiro进行登录验证
            subject.login(token);
            // 没出错则验证成功
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }


    // 从header中提取JWT Token
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String authorization = httpServletRequest.getHeader(JWTUtil.AUTH_HEADER);
        if (authorization.equals("") || !authorization.contains(".")) {
            return new AuthenticationToken() {
                @Override
                public Object getPrincipal() {
                    return null;
                }
                @Override
                public Object getCredentials() {
                    return null;
                }
            };
        }
        JWTToken token = new JWTToken(authorization);
        return token;
    }

    // isAccessAllowed()方法返回false，会进入该方法，表示拒绝访问
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());

        PrintWriter writer = httpServletResponse.getWriter();
        writer.write("{\"code\":401,\"msg\":\"UNAUTHORIZED\"}");
        fillCorsHeader(WebUtils.toHttp(request), httpServletResponse);
        return false;
    }

    // shiro利用JWT Token登录成功后，进入该方法
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        String newToken = null;

        // 登录成功后刷新token
        if (token instanceof JWTToken) {
            newToken = JWTUtil.refreshTokenExpired(token.getCredentials().toString(),JWTUtil.SECRET);
        }
        if (newToken != null) {
            httpServletResponse.setHeader(JWTUtil.AUTH_HEADER, newToken);
        }
        return true;
    }

    // 利用JWT Token登录失败会进入该方法
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        // 直接返回false，交给后面的onAccessDenied()方法处理
        return false;
    }

    // 跨域请求的解决方案之一
    public void fillCorsHeader(HttpServletRequest request, HttpServletResponse response) {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "*");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
    }

}
